A. Field of the Invention
This invention relates generally to class loading, and, more particularly, to methods and apparatus for ensuring type safe linkage of classes in an environment that employs multiple runtime name spaces, user-defined class loaders, and lazy loading of classes.
B. Description of the Related Art
Object-oriented programming techniques have revolutionized the computer industry. For example, such techniques offer new methods for designing and implementing computer programs using an application programming interface (API) with a predefined set of “classes,” each of which provides a template for the creation of “objects” sharing certain attributes determined by the class. These attributes typically include a set of data fields and a set of methods for manipulating the object.
The Java™ Development Kit (JDK) from Sun Microsystems, Inc., for example, enables developers to write object-oriented programs using an API with classes defined using the Java™ object-oriented programming language.1 The Java API consists of a class library having predefined classes. The class library defines a hierarchy of classes with a child class (i.e., subclass) inheriting attributes (i.e., fields and methods) of its parent class. Instead of having to write all aspects of a program from scratch, programmers can simply include selected classes from the API in their programs and extend the functionality offered by such classes as required to suit the particular needs of a program. This effectively reduces the amount of effort generally required for software development. 1 The Java™ programming language is an object-oriented programming language that is described, for example, in a text entitled “The Java Language Specification” by James Gosling, Bill Joy, and Guy Steele, Addison-Wesley, 1996. Sun, Sun Microsystems, the Sun Logo, Java, and JavaSoft and are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.
The JDK also includes a compiler and a runtime system with a virtual machine (VM) for executing programs.2 In general, software developers write programs in a programming language (in this case the Java programming language) that use classes from the API. Using the compiler, developers compile their programs into files containing “bytecodes.” The runtime system integrates the bytecode files with selected classes from the API into an executable application. The Java VM (JVM) then executes the application by converting bytecodes into appropriate instructions executable within a particular operating system/computer hardware. The Java VM thus acts like an abstract computing machine, receiving instructions from programs in the form of bytecodes and interpreting these bytecodes by dynamically converting them into a form for execution, such as object code, and executing them. 2 Details on the VM for the JDK can be found in a text entitled “The Java Virtual Machine Specification,” by Tim Lindholm and Frank Yellin, Addison Wesley, 1996.
The JDK also employs lazy loading, which means that software attributes or classes are only loaded when they are used for the first time, thereby reducing memory usage and improving system response time. During runtime, the JVM invokes one or more class loaders to load any necessary classes. Class loaders are objects that can be defined using the Java™ programming language and represent instances of the class ClassLoader. The ClassLoader.loadClass method accepts a class name as an argument, and returns a Class object that is the runtime representation of a class type. These class loaders may be user-defined; a user may create a class loader to specify, for example, a remote location from which a class is to be loaded or to assign security attributes to a class loaded from a particular source.
At compile time, a class type is typically defined by the name of the class; this is sufficient because the compiler uses a single namespace (i.e., a set of names in which all names are unique). At runtime, however, class loaders may introduce multiple namespaces. As a result, a class type during runtime is defined not by its name alone, but rather by the combination of the class name and its defining class loader.
For example, at compile time, a class named “C” is uniquely identified by its name; and it has a class type represented by C, where C specifies the class name. At runtime, if a class named C is loaded by a class loader L1, L1 is referred to as the defining class loader (or defining loader) for a class named C. Accordingly, at runtime, the type of a class named C is represented by <C, L1>, where C specifies the class name and L1 specifies the class's defining loader. In the same regard, <C, L3> represents a different class type than <C, L1>, even though the two classes share the same name C.
The JVM uses L1, the defining loader for <C, L1> to load not only <C, L1>, but also to load classes that are referenced by <C, L1>. Thus, for example, if <C, L1> references a class named “D,” and that class is loaded by L1, then the type of that class is <D, L1>. A class loader may, however, initiate the loading of a class but delegate to another class loader the task of actually loading the class. For example, L1, the defining loader for <C, L1>, may initiate the loading of the class named D, but may delegate the responsibility of actually loading the class named D to another class loader L2. This may be represented by <D, L2>L1, where D specifies the class name, L1 specifies the loader that initiated class loading (i.e., the initiating class loader), and L2 specifies D's defining loader. As used herein, notation such as DL1 refers to a class named D having an initiating loader L1. Thus, based on whether loader L1 itself loads the class named D or delegates that responsibility to L2, the class type may be either <D, L1> or <D, L2>; in fact, <D, L1> and <D, L2>completely different and unrelated classes having different methods or fields.
This situation may lead to a lack of type safe linkage. A program is type safe if it only operates on data using operations that are defined for the type of that data. The type safe linkage problem may be demonstrated via a simple example, using the sample software code presented below. For the sake of clarity, the class type notation described above is used where class names would normally appear.
class <C, L1> {void f( ) {<E, L1>L1 × = <D, L2>L1.g}}class <D, L2> {<E, L2>L2 g( ) {...}class <E, L2> {private int secret_value;}class <E, L1> {public int secret_value;}
Because <C, L1> is defined by L1, L1 is used to initiate the loading of classes named E and D referenced within <C, L1>. Although L1 is the defining loader for the class named E, L1 delegates to L2 the task of loading the class named D. As a result, L2 is the defining loader for the class named D, and is used by D.g to load the class named E. As it happens, the class named E loaded by L2 is different from the class named E loaded by L1. <C, L1> expects an instance of <E, L1> to be returned by D.g, but actually receives an instance of <E, L2>, which is a completely different class. Because of this difference, class <C, L1> is able to print the private field secret_value from an instance of <E, L2>, thereby compromising security. There exists, therefore, a need for a system that ensures type safe linkage in an environment that employs multiple runtime name spaces, user-defined class loaders, and lazy loading of classes.